package server

import (
	"encoding/json"
	"net/http"
	"server/components/httputils"
	"server/components/logger"
	"server/components/repository"
	"strconv"

	"github.com/go-chi/chi/v5"
)

// @Summary      Поиск учетных записей пользователей по полям
// @Description  Поиск учетных записей пользователей по полям. При указании нескольких полей, поиск идет по всем полям одновременно (т.е применяется операция AND)
// @Tags         accounts
// @Param        firstName	query      string  false  "firstName"
// @Param        lastName	query      string  false  "lastName"
// @Param        email	query      string  false  "email"
// @Produce      json
// @Success      200  {object}  []ent.User
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Router       /accounts/search [get]
func searchAccounts(w http.ResponseWriter, r *http.Request) {
	request := repository.SearchAccountsRequest{}
	if err := request.FromQuery(r.URL); err != nil {
		http.Error(w, "некорректный запрос", http.StatusBadRequest)
		return
	}
	users, err := repository.SearchAccounts(r.Context(), request)
	if err != nil {
		http.Error(w, "ошибка поиска аккаунтов", http.StatusInternalServerError)
		return
	}
	httputils.WriteJson(w, users)
}

// @Summary      Получение региона по id
// @Description  Получение региона по id
// @Tags         regions
// @Param        regionId	path      int64  true "regionId"
// @Produce      json
// @Success      200  {object}  repository.Region
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион не найден"
// @Router       /region/{regionId} [get]
func getRegionById(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "regionId"), 10, 64)
	if err != nil || id < 0 {
		http.Error(w, "Некорректный id региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	if res, err := repository.GetRegionByID(r.Context(), id); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, res)
	}
}

// @Summary      Добавление региона
// @Description  Добавление региона
// @Tags         regions
// @Param        data	body      repository.AddRegionRequest  true "region"
// @Produce      json
// @Success      200  {object}  repository.Region
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      409  {string}  error "Регион с такими latitude и longitude уже существует"
// @Router       /region [post]
func addRegion(w http.ResponseWriter, r *http.Request) {
	req := repository.AddRegionRequest{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Некорректные данные:"+err.Error(), http.StatusBadRequest)
		return
	}
	if res, err := repository.AddRegion(r.Context(), req); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, res)
	}
}

// @Summary      Обновление региона
// @Description  Обновление региона
// @Tags         regions
// @Param        regionId	path      int64  true "regionId"
// @Param        data	body      repository.UpdateRegionRequest  true "region"
// @Produce      json
// @Success      200  {object}  repository.Region
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион не найден"
// @Failure      409  {string}  error "Регион с такими latitude и longitude уже существует"
// @Router       /region/{regionId} [put]
func updateRegion(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "regionId"), 10, 64)
	if err != nil || id < 0 {
		http.Error(w, "Некорректный id региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	req := repository.UpdateRegionRequest{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		logger.Logger().Errorf("invalid data %v", err)
		http.Error(w, "Некорректные данные:"+err.Error(), http.StatusBadRequest)
		return
	}
	if res, err := repository.UpdateRegion(r.Context(), id, req); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, res)
	}
}

// @Summary      Удаление региона по id
// @Description  Удаление региона по id
// @Tags         regions
// @Param        regionId	path      int64  true "regionId"
// @Produce      json
// @Success      200  {object}  httputils.EmptyResponse
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион не найден"
// @Router       /region/{regionId} [delete]
func deleteRegion(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "regionId"), 10, 64)
	if err != nil || id < 0 {
		http.Error(w, "Некорректный id региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	if res, err := repository.DeleteRegion(r.Context(), id); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, res)
	}
}

// @Summary      Получение типа региона по id
// @Description  Получение типа региона по id
// @Tags         regions
// @Param        typeId	path      int64  true "typeId"
// @Produce      json
// @Success      200  {object}  repository.RegionType
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Тип региона не найден"
// @Router       /region/types/{typeId} [get]
func getRegionTypeById(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "typeId"), 10, 64)
	if err != nil || id < 0 {
		http.Error(w, "Некорректный id типа региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	if res, err := repository.GetRegionTypeByID(r.Context(), id); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, res)
	}
}

// @Summary      Добавление типа региона
// @Description  Добавление типа региона
// @Tags         regions
// @Param        data	body      repository.AddRegionTypeRequest  true "regionType"
// @Produce      json
// @Success      200  {object}  repository.RegionType
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      409  {string}  error "Такой тип региона уже существует"
// @Router       /region/types [post]
func addRegionType(w http.ResponseWriter, r *http.Request) {
	req := repository.AddRegionTypeRequest{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Некорректные данные:"+err.Error(), http.StatusBadRequest)
		return
	}
	if res, err := repository.AddRegionType(r.Context(), req); err != nil {
		err.Write(w)
		return
	} else {
		httputils.WriteJson(w, res)
	}
}

// @Summary      Обновление типа региона
// @Description  Обновление типа региона
// @Tags         regions
// @Param        typeId	path      int64  true "typeId"
// @Param        data	body      repository.UpdateRegionTypeRequest  true "regionType"
// @Produce      json
// @Success      200  {object}  repository.RegionType
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Тип региона не найден"
// @Failure      409  {string}  error "Такой тип региона уже существует"
// @Router       /region/types/{typeId} [put]
func updateRegionType(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "typeId"), 10, 64)
	if err != nil || id < 0 {
		http.Error(w, "Некорректный id типа региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	req := repository.UpdateRegionTypeRequest{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Некорректные данные:"+err.Error(), http.StatusBadRequest)
		return
	}
	if res, err := repository.UpdateRegionType(r.Context(), id, req); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, res)
	}
}

// @Summary      Удаление типа региона по id
// @Description  Удаление типа региона по id
// @Tags         regions
// @Param        typeId	path      int64  true "typeId"
// @Produce      json
// @Success      200  {object}  httputils.EmptyResponse
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион не найден"
// @Router       /region/types/{typeId} [delete]
func deleteRegionType(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "typeId"), 10, 64)
	if err != nil || id < 0 {
		http.Error(w, "Некорректный id типа региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	if res, err := repository.DeleteRegionType(r.Context(), id); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, res)
	}
}

// @Summary      Получение погоды по региону
// @Description  Получение погоды по региону
// @Tags         weather
// @Param        regionId	path      int64  true "regionId"
// @Produce      json
// @Success      200  {object}  repository.Weather
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Тип региона не найден"
// @Router       /region/weather/{regionId} [get]
func getWeatherByRegionId(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "regionId"), 10, 64)
	if err != nil || id < 0 {
		http.Error(w, "Некорректный id типа региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	if resp, err := repository.GetWeatherByRegionId(r.Context(), id); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, resp)
	}
}

// @Summary      Поиск погоды для регионов по полям
// @Description  Поиск погоды для регионов по полям
// @Tags         weather
// @Param        regionId		query      int64  false "regionId"
// @Param 		 startDateTime 	query      string false "Дата и время начала периода для поиска погодных условий, в формате ISO-8601"
// @Param 		 endDateTime 	query      string false "Дата и время конца периода для поиска погодных условий, в формате ISO-8601"
// @Param 		 weatherCondition query string false "Погодные условия: CLEAR, CLOUDY, RAIN, SNOW, FOG, STORM"
// @Param		 from	query	int	false "Количество элементов, которое необходимо пропустить для формирования страницы с результатами (по умолчанию 0)"
// @Param		 size	query	int	false "Количество элементов на странице (по умолчанию 10)"
// @Produce      json
// @Success      200  {object}  []repository.WeatherSearchResult
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион с regionId не найден или forecastId в данном регионе не найдена"
// @Router       /region/weather/search [get]
func searchWeather(w http.ResponseWriter, r *http.Request) {
	req := repository.SearchWeatherRequest{}
	if err := req.FromQuery(r.URL); err != nil {
		http.Error(w, "Некорректный запрос", http.StatusBadRequest)
		return
	}
	req.EnsureDefaults()
	if res, err := repository.SearchWeatherByRegion(r.Context(), req); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, res)
	}
}

// @Summary      Добавление погоды
// @Description  Добавление погоды
// @Tags         weather
// @Param        data		body      repository.AddWeatherRequest  false "Описание данных погоды в регионе"
// @Produce      json
// @Success      200  {object}  repository.Weather
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион с regionId не найден или forecastId в данном регионе не найдена"
// @Router       /region/weather [post]
func addWeather(w http.ResponseWriter, r *http.Request) {
	req := repository.AddWeatherRequest{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "некорректный запрос", http.StatusBadRequest)
		return
	}
	if weather, err := repository.AddWeather(r.Context(), req); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, weather)
	}
}

// @Summary      Обновление погоды для региона
// @Description  Обновление погоды для региона
// @Tags         weather
// @Param        data		body      repository.UpdateWeatherRequest  false "Описание обновления данных погоды в регионе"
// @Produce      json
// @Success      200  {object}  repository.Weather
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион с regionId не найден или forecastId в данном регионе не найдена"
// @Router       /region/weather/{regionId} [put]
func updateRegionWeather(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "regionId"), 10, 64)
	if err != nil || id < 0 {
		http.Error(w, "Некорректный id региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	req := repository.UpdateWeatherRequest{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "некорректный запрос", http.StatusBadRequest)
		return
	}
	if weather, err := repository.UpdateWeather(r.Context(), id, req); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, weather)
	}
}

// @Summary      Удаление погоды
// @Description  Удаление погоды
// @Tags         weather
// @Param        regionId	path      int64  true "regionId"
// @Produce      json
// @Success      200  {object}  httputils.EmptyResponse
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион с regionId не найден или forecastId в данном регионе не найдена"
// @Router       /region/weather/{regionId} [delete]
func deleteRegionWeather(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "regionId"), 10, 64)
	if err != nil || id < 0 {
		http.Error(w, "Некорректный id региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	if err := repository.DeleteWeatherForRegion(r.Context(), id); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, httputils.EmptyResponse{})
	}
}

// @Summary      Добавление погоды для конкретного региона
// @Description  Добавление погоды для конкретного региона
// @Tags         weather
// @Param        regionId	path      int64  true "regionId"
// @Param        weatherId	path      int64  true "weatherId"
// @Produce      json
// @Success      200  {object}  repository.Weather
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион с regionId не найден или forecastId в данном регионе не найдена"
// @Router       /region/{regionId}/weather/{weatherId} [post]
func setRegionWeather(w http.ResponseWriter, r *http.Request) {
	regionId, err := strconv.ParseInt(chi.URLParam(r, "regionId"), 10, 64)
	if err != nil || regionId < 0 {
		http.Error(w, "Некорректный id региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	weatherId, err := strconv.ParseInt(chi.URLParam(r, "weatherId"), 10, 64)
	if err != nil || weatherId < 0 {
		http.Error(w, "Некорректный id погоды:"+err.Error(), http.StatusBadRequest)
		return
	}
	if weather, err := repository.SetRegionWeather(r.Context(), regionId, weatherId); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, weather)
	}
}

// @Summary      Удаление погоды для конкретного региона
// @Description  Удаление погоды для конкретного региона
// @Tags         weather
// @Param        regionId	path      int64  true "regionId"
// @Param        weatherId	path      int64  true "weatherId"
// @Produce      json
// @Success      200  {object}  repository.Region
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Регион с regionId не найден или forecastId в данном регионе не найдена"
// @Router       /region/{regionId}/weather/{weatherId} [delete]
func unsetRegionWeather(w http.ResponseWriter, r *http.Request) {
	regionId, err := strconv.ParseInt(chi.URLParam(r, "regionId"), 10, 64)
	if err != nil || regionId < 0 {
		http.Error(w, "Некорректный id региона:"+err.Error(), http.StatusBadRequest)
		return
	}
	weatherId, err := strconv.ParseInt(chi.URLParam(r, "weatherId"), 10, 64)
	if err != nil || weatherId < 0 {
		http.Error(w, "Некорректный id погоды:"+err.Error(), http.StatusBadRequest)
		return
	}
	if region, err := repository.UnsetRegionWeather(r.Context(), regionId, weatherId); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, region)
	}
}

// @Summary      Получение прогноза погоды
// @Description  Получение прогноза погоды
// @Tags         weather
// @Param        forecastId	path      int64  true "forecastId"
// @Produce      json
// @Success      200  {object}  repository.Weather
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Прогноза с таким forecastId не существует "
// @Router       /region/weather/forecast/{forecastId} [get]
func getForecast(w http.ResponseWriter, r *http.Request) {
	fId, err := strconv.ParseInt(chi.URLParam(r, "forecastId"), 10, 64)
	if err != nil || fId < 0 {
		http.Error(w, "Некорректный id прогноза:"+err.Error(), http.StatusBadRequest)
		return
	}
	if fc, err := repository.GetForecastByID(r.Context(), fId); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, fc)
	}
}

// @Summary      Добавление прогноза погоды
// @Description  Добавление прогноза погоды
// @Tags         weather
// @Param        data		body      repository.ForecastAddRequest  false "Описание данных прогноза погоды"
// @Produce      json
// @Success      200  {object}  repository.Forecast
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Прогноза с таким forecastId не существует "
// @Router       /region/weather/forecast [post]
func addForecast(w http.ResponseWriter, r *http.Request) {
	req := repository.ForecastAddRequest{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		logger.Logger().Errorf("Failed to parse forecast %v", err)
		http.Error(w, "некорректный запрос", http.StatusBadRequest)
		return
	}
	if f, err := repository.AddForecast(r.Context(), req); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, f)
	}
}

// @Summary      Обновление прогноза погоды
// @Description  Обновление прогноза погоды
// @Tags         weather
// @Param        forecastId	path      int64  true "forecastId"
// @Param        data		body      repository.ForecastUpdateRequest  false "Описание данных обновления прогноза погоды"
// @Produce      json
// @Success      200  {object}  repository.Forecast
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Прогноза с таким forecastId не существует "
// @Router       /region/weather/forecast/{forecastId} [put]
func updateForecast(w http.ResponseWriter, r *http.Request) {
	fId, err := strconv.ParseInt(chi.URLParam(r, "forecastId"), 10, 64)
	if err != nil || fId < 0 {
		http.Error(w, "Некорректный id прогноза:"+err.Error(), http.StatusBadRequest)
		return
	}
	req := repository.ForecastUpdateRequest{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "некорректный запрос", http.StatusBadRequest)
		return
	}
	if f, err := repository.UpdateForecastByID(r.Context(), fId, req); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, f)
	}
}

// @Summary      Удаление прогноза погоды
// @Description  Удаление прогноза погоды
// @Tags         weather
// @Param        forecastId	path      int64  true "forecastId"
// @Produce      json
// @Success      200  {object}  httputils.EmptyResponse
// @Failure		 400  {string}	error "Некорректный запрос"
// @Failure      401  {string}  error "Пользователь не авторизирован"
// @Failure      404  {string}  error "Прогноза с таким forecastId не существует "
// @Router       /region/weather/forecast/{forecastId} [delete]
func deleteForecast(w http.ResponseWriter, r *http.Request) {
	fId, err := strconv.ParseInt(chi.URLParam(r, "forecastId"), 10, 64)
	if err != nil || fId < 0 {
		http.Error(w, "Некорректный id прогноза:"+err.Error(), http.StatusBadRequest)
		return
	}
	if err := repository.DeleteForecastByID(r.Context(), fId); err != nil {
		err.Write(w)
	} else {
		httputils.WriteJson(w, httputils.EmptyResponse{})
	}
}
